/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.netty.example.http.helloworld;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;

/**
 * HTTP常用请求头：
 *  1. Connection: keep-alive、Content-Type、Content-Length、Content-*和Accept-*。
 *      HttpServerKeepAliveHandler、HttpContentCompressor
 *  2. Transfer-Encoding: chunked 带有该消息头的HTTP消息体分块进行传输。
 *      HttpServerCodec、HttpObjectAggregator
 *  3. 浏览器缓存相关:
 *      Expires: 过期时间，可以由Cache-Control代替。
 *      Pragma: 功能和Cache-Control相似，已经逐步抛弃，如果一个报文中同时出现Pragma和Cache-Control时，以Pragma为准。
 *      Cache-Control:
 *          public: 表示响应可以被客户端和代理服务器缓存。
 *          private: 表示响应只能被客户端缓存。
 *          no-store: 不缓存任何响应。
 *          no-cache: 资源被缓存，但是立即失效，下次会发起请求验证资源是否过期。
 *              意味着每次发送请求静态资源时都需要向服务端进行一次过期认证，通常情况下，
 *              过期认真证需要配合（ETag和Last-Modified）进行一个比较，
 *              如果验证并没有过期，则会发送304的状态码，通知浏览进复用浏览器的缓存。
 *          max-age=30: 缓存30秒后就过期，需要重新请求。
 *          s-maxage=30: 覆盖max-age，作用一样，只在代理服务器中生效。
 *          max-stale=30: 30秒内，即使缓存过期，也可以使用该缓存。
 *          min-fresh=30: 希望在30秒内获取最新的响应。
 *      Last-Modified、If-Modified-Since:
 *          在浏览器第一次请求某一个URL时，服务器端的返回状态会是200，内容是客户端请求的资源，
 *          同时有一个Last-Modified的属性标记此文件在服务器端最后被修改的时间。
 *          客户端第二次请求此URL时，根据HTTP协议的规定，浏览器会向服务器传送If-Modified-Since报头，询问该时间之后文件是否有被修改过。
 *      ETag和If-None-Match:
 *          1、一些文件也许会周期性的更改，但是他的内容并不改变(仅仅改变的修改时间)，这个时候我们并不希望客户端认为这个文件被修改了，而重新GET;
 *          2、某些文件修改非常频繁，比如在秒以下的时间内进行修改，(比方说1s内修改了N次)，If-Modified-Since能检查到的粒度是s级的，
 *          这种修改无法判断(或者说UNIX记录MTIME只能精确到秒)
 *          3、某些服务器不能精确的得到文件的最后修改时间；
 *  4. Range、Content-Range: 传输指定bytes范围的数据，用于多线程下载和断点续传。
 *  5. Expect: 100-continue: HttpServerExpectContinueHandler
 *      POST请求的请求体大于一定值时，先发送一个包含该请求头的请求，询问Server是否愿意接受数据。
 *      服务端返回100 Continue表示可以接收，417 Expectation Failed拒绝接收请求。
 *  6. 跨域资源共享相关设置。
 *      Origin: 表示当前请求资源所在页面的协议、域名和端口。
 *          Host: 表示当前请求要被发送的目的地，即当前请求目标资源，仅包括域名和端口号。
 *          Referer: 表示当前请求资源所在页面的完整路径：协议+域名+查询参数（注意不包含锚点信息）。
 *      Access-Control-Allow-Origin: 服务端允许访问的域名。它的值要么是请求时Origin字段的值，要么是一个*，表示接受任意域名的请求。
 *      Access-Control-Allow-Credentials: 表示是否允许发送Cookie。默认情况下，Cookie不包括在CORS请求之中。
 *          设为true，即表示服务器明确许可，Cookie可以包含在请求中，一起发给服务器。这个值也只能设为true，如果服务器不要浏览器发送Cookie，删除该字段即可。
 *      Access-Control-Expose-Headers: CORS请求时，XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段：
 *      Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。
 *          如果想拿到其他字段，就必须在Access-Control-Expose-Headers里面指定。
 *  7. Cookie和Token。
 *      HTTP响应报文通过Set-Cookie通知客户端需要保存如下Cookie，可以含多个Set-Cookie项。浏览器在发送请求时会通过Cookie请求头返回该域名下接收的所有Cookie。
 *      服务端生成Token发送给客户端，客户端将Token保存在Cookie或者localStorage里面，再次请求时将Token的值设置到Authorization请求头中带给服务器端。
 *  8. Connection: Upgrade、Upgrade: WebSocket
 */
public final class HttpHelloWorldServer {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", SSL? "8443" : "8080"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.option(ChannelOption.SO_BACKLOG, 1024);
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new HttpHelloWorldServerInitializer(sslCtx));

            Channel ch = b.bind(PORT).sync().channel();

            System.err.println("Open your web browser and navigate to " +
                    (SSL? "https" : "http") + "://127.0.0.1:" + PORT + '/');

            ch.closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
